package net.kosev.battleships.model;

import java.io.Serializable;

import net.kosev.battleships.model.creators.AutoBoardCreator;

/**
 * Game class. Implements game logic like tow players making moves
 * 
 * @author Nikola
 *
 */
public class Game implements Serializable {
	
	/**
	 * Serialization id
	 */
	private static final long serialVersionUID = -8632071672530064878L;

	/**
	 * Player board instance
	 */
	private Board playerBoard = null;
	
	/**
	 * Enemy board instance
	 */
	private Board enemyBoard = null;
	
	/**
	 * Competitor which should make the next firing move
	 */
	private int onTurn = Competitor.NONE;
	
	/**
	 * Competitor that won current game
	 */
	private int winner = Competitor.NONE;
	
	/**
	 * Game Listener (Observable pattern)
	 */
	private GameListener listener = null;
	
	/**
	 * Create new game and initialize two boards
	 * 
	 * @throws BoardException	Exception in board creation
	 */
	public void newGame() throws BoardException {
		onTurn = Competitor.NONE;
		
		playerBoard = createBoard();
		enemyBoard = createBoard();
		
		playerBoard.setBoardListener(new BoardListener() {
			public void boardDestroyed(Board board) {
				endGame(Competitor.ENEMY);
			}
		});
		
		enemyBoard.setBoardListener(new BoardListener() {
			public void boardDestroyed(Board board) {
				endGame(Competitor.PLAYER);
			}
		});
	}
	
	/**
	 * Finish current game and set the winner
	 * 
	 * @param winner Competitor that won the game
	 */
	private void endGame(int winner) {
		this.winner = winner;
		if (listener != null) {
			listener.gameEnded(this);
		}
	}
	
	/**
	 * Regenerate one of the two boards
	 * 
	 * @param competitor Board to regenerate
	 * 
	 * @throws GameException	Exception on wrong competitor
	 * @throws BoardException	Exception on board creation failure
	 */
	public void recreateBoard(int competitor) throws GameException, BoardException {
		if (onTurn != Competitor.NONE) {
			throw new GameException("Cannot recreate board when game has been started");
		}
		
		if (competitor != Competitor.PLAYER && competitor != Competitor.ENEMY) {
			throw new GameException("Cannot recreate board of unknown competitor");
		}
		
		if (competitor == Competitor.PLAYER) {
			playerBoard = createBoard();
		} else {
			enemyBoard = createBoard();
		}
	}
	
	/**
	 * Create board
	 * 
	 * @return	Currently created board
	 * @throws BoardException	Exception on board creation failure
	 */
	private Board createBoard() throws BoardException {
		return Board.create(new AutoBoardCreator());
	}
	
	/**
	 * The player makes firing move
	 * 
	 * @param row		Board row to fire
	 * @param column	Board column to fire
	 * 
	 * @throws BoardException	Exception on wrong firing position
	 * @throws GameException	Exception on wrong move
	 */
	public void playerFires(int row, int column) throws BoardException, GameException {
		if (onTurn == Competitor.ENEMY) {
			throw new GameException("Player cannot fire on enemy's turn");
		}
		
		boolean success = genericFire(enemyBoard, row, column);
		onTurn = Competitor.ENEMY;
		
		if (listener != null) {
			listener.playerFired(this, success);
		}
	}
	
	/**
	 * The enemy makes firing move
	 * 
	 * @param row		Board row to fire
	 * @param column	Board column to fire
	 * 
	 * @throws BoardException	Exception on wrong firing position
	 * @throws GameException	Exception on wrong move
	 */
	public void enemyFires(int row, int column) throws BoardException, GameException {
		if (onTurn == Competitor.PLAYER) {
			throw new GameException("Enemy cannot fire on player's turn");
		}
		
		boolean success = genericFire(playerBoard, row, column);
		onTurn = Competitor.PLAYER;
		
		if (listener != null) {
			listener.enemyFired(this, success);
		}
	}
	
	/**
	 * Generic invocation of board's fire method
	 * 
	 * @param board		The board instance to fire at
	 * @param row		Board row to fire
	 * @param column	Board column to fire
	 * 
	 * @return	True on hit, false on miss
	 * 
	 * @throws BoardException	Exception on wrong firing position
	 */
	private boolean genericFire(Board board, int row, int column) throws BoardException {
		return board.fire(row, column);
	}
	
	/**
	 * Sets Game Listener
	 * 
	 * @param listener The GameListener instance
	 */
	public void setGameListener(GameListener listener) {
		this.listener = listener;
	}
	
	/**
	 * Gets player's board instance
	 * 
	 * @return	Player's board instance
	 */
	public Board getPlayerBoard() {
		return playerBoard;
	}
	
	/**
	 * Gets enemy's board instance
	 * 
	 * @return	Board instance
	 */
	public Board getEnemyBoard() {
		return enemyBoard;
	}
	
	/**
	 * Gets current game winner
	 * 
	 * @return	Game winner
	 */
	public int getWinner() {
		return winner;
	}
	
}
